/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.bef.core.buffer;

import com.inspur.edp.cef.entity.changeset.AddChangeDetail;
import com.inspur.edp.cef.entity.changeset.DeleteChangeDetail;
import com.inspur.edp.cef.entity.changeset.IChangeDetail;
import com.inspur.edp.cef.entity.changeset.ModifyChangeDetail;
import com.inspur.edp.cef.entity.changeset.Util;
import com.inspur.edp.cef.entity.changeset.ValueObjModifyChangeDetail;
import com.inspur.edp.cef.entity.entity.ICefData;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.entity.entity.IEntityDataCollection;
import com.inspur.edp.cef.entity.entity.IValueObjData;
import com.inspur.edp.cef.entity.entity.dynamicProp.IDynamicPropSet;
import java.util.Map;
import java.util.Objects;

public class UndoChangeDetailMerger {
  private UndoChangeDetailMerger() {}

  private static UndoChangeDetailMerger privateInstance = new UndoChangeDetailMerger();

  public static UndoChangeDetailMerger getInstance() {
    return privateInstance;
  }

  /// #region BuildUndoChange
  public final IChangeDetail buildUndoChange(
      IChangeDetail source, IEntityData entityData, IChangeDetail existing) {
    Objects.requireNonNull(source, "source");

    switch (source.getChangeType()) {
      case Added:
        return reverseAddChangeDetail((AddChangeDetail) source, entityData, existing);
      case Deleted:
        return reverseDeleteChangeDetail((DeleteChangeDetail) source, entityData, existing);
      case Modify:
        return reverseModifyChangeDetail((ModifyChangeDetail) source, entityData, existing);
      default:
        throw new RuntimeException();
    }
  }

  private IChangeDetail reverseModifyChangeDetail(
      ModifyChangeDetail source, IEntityData data, IChangeDetail existing) {
    Objects.requireNonNull(data, "data");
    ModifyChangeDetail result = null;
    if (existing != null) {
      switch (existing.getChangeType()) {
        case Deleted:
          return existing;
        case Added:
          throw new RuntimeException();
        case Modify:
          result = (ModifyChangeDetail) existing;
          break;
      }
    } else {
      result = new ModifyChangeDetail(source.getID());
    }

    for (Map.Entry<String, Object> changePair : source.getPropertyChanges().entrySet()) {
      if (changePair.getValue() instanceof IValueObjData) {
        throw new RuntimeException("Bef事务内不允许对Udt字段整体赋值:" + changePair.getKey());
      }
      boolean contained = result.getPropertyChanges().containsKey(changePair.getKey());
      Object containedValue = result.getPropertyChanges().get(changePair.getKey());
      if (changePair.getValue() instanceof ValueObjModifyChangeDetail) {
        if (!contained) {
          result
              .getPropertyChanges()
              .put(
                  changePair.getKey(),
                  revertValueObjChangeDetail(
                      (ValueObjModifyChangeDetail) changePair.getValue(),
                      (ICefData) data.getValue(changePair.getKey()),
                      (ValueObjModifyChangeDetail) containedValue));
        }
      } else if (contained) {
        continue;
      } else {

        Object currentValue = data.getValue(changePair.getKey());
        result.getPropertyChanges().put(changePair.getKey(), currentValue);
      }
    }
    if (source.getChildChanges() != null) {

      for (Map.Entry<String, Map<String, IChangeDetail>> childsPair :
          source.getChildChanges().entrySet()) {
        IEntityDataCollection entityDataChilds = data.getChilds().get(childsPair.getKey());

        for (Map.Entry<String, IChangeDetail> childPair : childsPair.getValue().entrySet()) {
          result.addChildChangeSet(
              childsPair.getKey(),
              buildUndoChange(
                  childPair.getValue(),
                  entityDataChilds.tryGet(childPair.getKey()),
                  existing != null
                      ? Util.getChildChange(existing, childsPair.getKey(), childPair.getKey())
                      : null));
        }
      }
    }
    return result;
  }

  private IChangeDetail revertValueObjChangeDetail(
      ValueObjModifyChangeDetail source, ICefData data, ValueObjModifyChangeDetail existing) {
    if (existing == null) {
      ValueObjModifyChangeDetail tempVar = new ValueObjModifyChangeDetail();
      tempVar.setData((IValueObjData) data.copy());
      existing = tempVar;
    }

    for (Map.Entry<String, Object> changePair : source.getPropertyChanges().entrySet()) {
      if (changePair.getValue() instanceof ICefData) {
        throw new RuntimeException("Bef事务内不允许对Udt字段整体赋值:" + changePair.getKey());
      }

      boolean contained = existing.getPropertyChanges().containsKey(changePair.getKey());
      Object containedValue = existing.getPropertyChanges().get(changePair.getKey());
      if (changePair.getValue() instanceof ValueObjModifyChangeDetail) {
        if (!contained) {
          existing
              .getPropertyChanges()
              .put(
                  changePair.getKey(),
                  revertValueObjChangeDetail(
                      (ValueObjModifyChangeDetail) changePair.getValue(),
                      (ICefData) data.getValue(changePair.getKey()),
                      (ValueObjModifyChangeDetail) containedValue));
        }
      } else if (contained) {
        continue;
      } else {
        Object currentValue;
        if(data instanceof IDynamicPropSet) {
          currentValue = ((IDynamicPropSet) data).contains(changePair.getKey()) ? data
              .getValue(changePair.getKey()) : null;
        } else {
          currentValue = data.getValue(changePair.getKey());
        }
        existing.getPropertyChanges().put(changePair.getKey(), currentValue);
      }
    }
    return existing;
  }

  private IChangeDetail reverseDeleteChangeDetail(
      DeleteChangeDetail source, IEntityData entityData, IChangeDetail existing) {
    if (existing != null) {
      switch (existing.getChangeType()) {
        case Deleted:
          return null;
        case Modify:
          return new AddChangeDetail((IEntityData) entityData.copy());
        case Added:
          throw new RuntimeException();
      }
    }
    return new AddChangeDetail((IEntityData) entityData.copy());
  }

  private IChangeDetail reverseAddChangeDetail(
      AddChangeDetail source, IEntityData entityData, IChangeDetail existing) {
    if (existing != null) {
      throw new RuntimeException();
    }
    return new DeleteChangeDetail(source.getID());
  }

  /// #endregion
}
